1 inicio

3 MetodologĆ­a

El presente estudio siguió un enfoque de aprendizaje supervisado orientado a la clasificación binaria (pagado / incumplido). El procedimiento general consistió en aplicar dos familias de clasificadores (Logit y KNN) sobre datos previamente preparados, y evaluar su desempeño mediante métricas complementarias y procedimientos de validación.

La implementación se realizó en R empleando flujos estandarizados de preprocesamiento y modelado. Para reducir la influencia de escalas dispares y valores extremos, se aplicaron filtros sobre outliers relevantes y se normalizaron las variables numéricas (centrado y escala) cuando correspondió; dicho escalado fue especialmente crítico para KNN, dada su dependencia de distancias euclidianas. La muestra final fue construida de forma estratificada para asegurar balance entre las clases objetivo, y todas las operaciones aleatorias (muestreo y particionado); ademÔs, se fijó la semilla (set.seed(28)) para garantizar reproducibilidad.

En cuanto al ajuste de modelos, la regresión logĆ­stica se estimó mediante glm(…, family = binomial()), obteniendo probabilidades predictivas que permitieron tanto el anĆ”lisis de coeficientes como la construcción de curvas ROC y la determinación de umbrales operativos (Ć­ndice de Youden). El KNN se abordó de dos maneras; una implementación bĆ”sica con class::knn, evaluando k en un rango (k = 1:100) y seleccionando el k que maximizó la exactitud fuera de muestra; y una versión integrada en caret::train() que incorporó validación cruzada estratificada (5 folds), bĆŗsqueda automĆ”tica de hiperparĆ”metros (tuneLength) y optimización segĆŗn el AUC (mĆ©trica ROC), lo que permitió una selección de modelo mĆ”s robusta frente a la variabilidad de los datos.

3.1 Definición de las variables

Las variables utilizadas en el estudio se organizan en dos grupos: dependientes e independientes. La variable dependiente seleccionada es el estado de pago de la persona (Estado), construida a partir del indicador Default y se codificó como factor con niveles ā€œPagadoā€ (no default) y ā€œIncumplidoā€ (default). Esta variable refleja el resultado del contrato crediticio y es una medida directa del riesgo de incumplimiento, por lo que su correcta definición y codificación es central para cualquier ejercicio de scoring, ya que no solo indica la ocurrencia del impago, sino que tambiĆ©n sirve como referencia para estimar probabilidades de default y calibrar los umbrales de decisión en procesos de aprobación crediticia.

Entre las variables independientes se incorporaron predictores financieros y de propósito del préstamo que, según la teoría del riesgo y la prÔctica del credit scoring, guardan relación con la capacidad de pago y la propensión al incumplimiento. El ingreso anual declarado por el solicitante Ingreso se emplea como variable de la capacidad de repago; a mayor ingreso disponible se espera una menor probabilidad de default, dado que permite absorber obligaciones adicionales y enfrentar eventos adversos sin perder la capacidad de servicio de la deuda. No obstante, la medida declarativa del ingreso puede presentar sesgos por subdeclaración o variabilidad temporal, por lo que su interpretación debe hacerse con cuidado.

La Relacion deuda/ingreso sintetiza la carga financiera del solicitante al relacionar obligaciones vigentes con su ingreso. Un DTI (Debt-to-Income, que viene siendo la relación deuda/ingreso) elevado indica que una fracción significativa del ingreso ya estÔ comprometida con otras deudas, lo que incrementa la vulnerabilidad ante shocks y aumenta la probabilidad de incumplimiento. Se toma en cuenta ya que permite captar no solo la magnitud del endeudamiento sino también la presión relativa sobre la liquidez del hogar o individuo.

El monto solicitado Monto prestamo incorpora la dimensión contractual del crédito, que son los préstamos de mayor cuantía: los cuales, sin ajustes proporcionales en condiciones o capacidad de pago, tienden a elevar el riesgo de default por aumentar la carga mensual y alargar el horizonte de exposición. AdemÔs, el monto solicitado puede interactuar con otras variables (por ejemplo, ingreso o FICO) para dibujar perfiles diferenciados de riesgo. Su inclusión facilita distinguir situaciones en las que un mismo monto resulta asumible o riesgoso según el contexto financiero del solicitante.

El puntaje crediticio Puntaje FICO funciona como un indicador consolidado del historial crediticio y de la probabilidad observada de cumplimiento en períodos previos. Puntajes mÔs altos se asocian sistemÔticamente con menor probabilidad de impago, pues reflejan comportamientos de pago estables, menor incidencia de morosidad previa y hÔbitos financieros mÔs conservadores. Por su carÔcter informativo y su uso extendido en la industria, el puntaje FICO aporta a la discriminación del riesgo y suele mostrar efectos significativos en modelos paramétricos y no paramétricos.

El propósito del préstamo reagrupado Proposito agrupado captura el destino del crédito, como lo puede ser una consolidación de deuda, compra de vivienda o vehículo, inversión en negocio, educación. Refleja diferencias cualitativas en la naturaleza y prioridad del gasto. Distintos propósitos implican perfiles de riesgo heterogéneos, es decir, un préstamo para consolidación de deuda puede indicar una situación financiera tensionada, mientras que un préstamo para inversión productiva o educación puede asociarse a retornos que faciliten el repago.

variables_modelo <- data.frame(
  Variable = c("Estado", "Ingreso", "Relación deuda/ingreso",
               "Monto préstamo", "Puntaje FICO", "Propósito", "Binaria"),
  
  Descripción = c(
    "Variable dependiente que representa el resultado final del crƩdito.",
    "Ingreso anual declarado por el solicitante, indicador de capacidad de pago.",
    "Ratio financiero que mide la carga de endeudamiento frente al ingreso.",
    "Valor del prƩstamo solicitado por el cliente.",
    "Puntaje crediticio que resume el historial de crƩdito del solicitante.",
    "Motivo declarado del prƩstamo, agrupado en categorƭas mayores.",
    "Versión numérica de la variable dependiente usada en el modelo."
  ),
  
  Tipo_Variable = c(
    "Categórica (binaria)",
    "Cuantitativa continua",
    "Cuantitativa continua",
    "Cuantitativa continua",
    "Cuantitativa continua",
    "Categórica (agrupada)",
    "Binaria (numƩrica)"
  ),
  
  Ejemplos_Notas = c(
    "Ej: 'Pagado', 'Incumplido'.",
    "En dólares anuales.",
    "Proporción (ej. 0.35).",
    "Monto del prƩstamo en USD.",
    "Rango tĆ­pico: 300 – 850.",
    "Ej: 'Consolidación', 'Negocio', 'Otros'.",
    "0 = Paga, 1 = No paga."
  )
)


tabla_variables <- kable(
  variables_modelo,
  format = "html",
  col.names = c("Variable", "Descripción", "Tipo de Variable", "Ejemplos / Notas"),
  align = c('l', 'l', 'l', 'l'),
  caption = "Tabla 1. Variables utilizadas en el Modelo de Riesgo Crediticio"
) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width = FALSE,
    font_size = 14,
    position = "center"
  ) %>%
  row_spec(0, background = "#990000", color = "white", bold = TRUE) %>%
  row_spec(1:7, background = "#FFFDF5") %>%
  column_spec(1, bold = TRUE, width = "3cm") %>%
  column_spec(2, width = "6cm") %>%
  column_spec(3, width = "3cm") %>%
  column_spec(4, width = "3.5cm") %>%
  footnote(
    general = "Elaboración propia con base en el dataset Lending Club (2007-2018).",
    general_title = "Fuente: ",
    footnote_as_chunk = TRUE
  )

tabla_variables
Tabla 1. Variables utilizadas en el Modelo de Riesgo Crediticio
Variable Descripción Tipo de Variable Ejemplos / Notas
Estado Variable dependiente que representa el resultado final del crĆ©dito. Categórica (binaria) Ej: ā€˜Pagado’, ā€˜Incumplido’.
Ingreso Ingreso anual declarado por el solicitante, indicador de capacidad de pago. Cuantitativa continua En dólares anuales.
Relación deuda/ingreso Ratio financiero que mide la carga de endeudamiento frente al ingreso. Cuantitativa continua Proporción (ej. 0.35).
Monto prƩstamo Valor del prƩstamo solicitado por el cliente. Cuantitativa continua Monto del prƩstamo en USD.
Puntaje FICO Puntaje crediticio que resume el historial de crĆ©dito del solicitante. Cuantitativa continua Rango tĆ­pico: 300 – 850.
Propósito Motivo declarado del prĆ©stamo, agrupado en categorĆ­as mayores. Categórica (agrupada) Ej: ā€˜Consolidación’, ā€˜Negocio’, ā€˜Otros’.
Binaria Versión numérica de la variable dependiente usada en el modelo. Binaria (numérica) 0 = Paga, 1 = No paga.
Fuente: Elaboración propia con base en el dataset Lending Club (2007-2018).

3.2 Base de datos

lending_raw <- read_csv("LC_loans_granting_model_dataset.csv", guess_max = 20000)

lending_base <- lending_raw %>%
  select(revenue, dti_n, loan_amnt, fico_n, Default, purpose, issue_d) %>%
  rename(
    ingreso = revenue,
    relacion_deuda_ingreso = dti_n,
    monto_prestamo = loan_amnt,
    puntaje_fico = fico_n,
    estado_pago = Default,
    proposito = purpose
  ) %>%
  mutate(
    fecha_emision = parse_date_time(issue_d, orders = "b-Y", locale = "en_US"),
    AƱo = year(fecha_emision),
    proposito = as.factor(proposito),
    proposito_agrupado = fct_collapse(
      proposito,
      Consolidacion = c("debt_consolidation", "credit_card"),
      "Casa/Vehiculo" = c("home_improvement", "major_purchase", "car", "house"),
      "Negocio/Estudio" = c("small_business", "educational")
    ),
    proposito_agrupado = fct_other(
      proposito_agrupado,
      keep = c("Consolidacion", "Casa/Vehiculo", "Negocio/Estudio"),
      other_level = "Otros"
    ),
    estado_pago = fct_recode(as.factor(estado_pago), "Pagado" = "0", "Incumplido" = "1")
  ) %>%
  select(-proposito, -issue_d, -fecha_emision) %>% 
  filter(ingreso <= 250000, relacion_deuda_ingreso <= 50) %>%
  drop_na()

set.seed(0408)


count_pagado <- sum(lending_base$estado_pago == "Pagado")
count_incumplido <- sum(lending_base$estado_pago == "Incumplido")

sample_size <- min(5000, count_pagado, count_incumplido)

paga <- lending_base %>% 
  filter(estado_pago == "Pagado") %>% 
  sample_n(sample_size)

nopaga <- lending_base %>% 
  filter(estado_pago == "Incumplido") %>% 
  sample_n(sample_size)

Base_datos <- bind_rows(paga, nopaga) %>% 
  select(AƱo, ingreso, relacion_deuda_ingreso, monto_prestamo, puntaje_fico, proposito_agrupado, estado_pago) %>%
  rename(
    Ingreso = ingreso,
    Relacion_deuda_ingreso = relacion_deuda_ingreso,
    Monto_prestado = monto_prestamo,
    FICO = puntaje_fico,
    Proposito = proposito_agrupado,
    Estado = estado_pago
  )

Base_datos <- Base_datos[sample(nrow(Base_datos)), ] %>% 
  mutate(Relacion_deuda_ingreso=Relacion_deuda_ingreso/100)


Base_datos_formateada <- Base_datos %>%
  mutate(
    Ingreso_formateado = scales::dollar(Ingreso, accuracy = 1),
    Monto_prestado_formateado = scales::dollar(Monto_prestado, accuracy = 1),
    Relacion_deuda_ingreso_formateado = scales::percent(Relacion_deuda_ingreso, accuracy = 0.1),
    FICO_formateado = as.integer(FICO)
  )

tabla_interactiva <- Base_datos %>%
  datatable(
    colnames = c(
      "AƱo",
      "Ingreso Anual (USD)", 
      "Relación Deuda/Ingreso", 
      "Monto del PrƩstamo (USD)",
      "Puntaje FICO", 
      "Propósito del Préstamo", 
      "Estado de Pago"
    ),
    filter = 'top',
    extensions = c('Buttons', 'Scroller'),
    options = list(
      pageLength = 10,
      dom = 'Bfrtip',
      scrollX = TRUE,
      scrollY = "400px",
      scroller = TRUE,
      buttons = list('copy', 'csv', 'excel', 'pdf', 'print'),
      language = list(
        url = '//cdn.datatables.net/plug-ins/1.10.25/i18n/Spanish.json'
      )
    ),
    caption = htmltools::tags$caption(
      style = 'caption-side: top; text-align: center; color: #FF0000; font-family: "Playfair Display"; font-size: 1.3rem; font-weight: bold;',
      'Tabla 2. Base de Datos de PrƩstamos - Dataset Lending Club'
    ),
    class = 'row-border stripe hover order-column',
    rownames = FALSE,
    width = '100%'
  ) %>%
  formatCurrency(
    columns = c('Ingreso', 'Monto_prestado'),
    currency = '$', digits = 0, before = TRUE
  ) %>%
  formatRound(
    columns = 'FICO',
    digits = 0
  ) %>%
  formatStyle(
    'Estado',
    target = 'row',
    backgroundColor = styleEqual('Incumplido', '#FFE6E6')
  ) %>%
  formatStyle(
    'Estado',
    backgroundColor = styleEqual('Incumplido', '#FF6B6B'),
    color = styleEqual('Incumplido', 'white'),
    fontWeight = styleEqual('Incumplido', 'bold')
  ) %>%
  formatStyle(
    'Relacion_deuda_ingreso',
    backgroundColor = styleInterval(
      cuts = c(30, 40), 
      values = c('white', '#FFF0F0', '#FFB8B8')
    )
  ) %>%
  formatStyle(
    'FICO',
    backgroundColor = styleInterval(
      cuts = c(600, 700),
      values = c('#FF6B6B', '#FFD8D8', 'white')
    )
  )


tabla_interactiva <- tabla_interactiva %>%
  htmlwidgets::prependContent(
    htmltools::tags$style(
      htmltools::HTML("
        table.dataTable thead th {
          background-color: #FF0000 !important;
          color: white !important;
          font-weight: bold !important;
        }
      ")
    )
  )

tabla_interactiva

4 Analisis descriptivo

El conjunto de datos analizado corresponde a registros de préstamos otorgados por la plataforma Lending Club, e incluye información relevante sobre las características financieras de los solicitantes y el comportamiento de pago asociado a cada crédito.

Con el fin de comprender de manera preliminar la composición y variabilidad de las observaciones, se presenta a continuación un resumen de las principales estadísticas descriptivas y la distribución de frecuencias por estado de pago.

resumen_general <- Base_datos %>%
  select(Ingreso, Relacion_deuda_ingreso, Monto_prestado, FICO) %>%
  mutate(Relacion_deuda_ingreso=Relacion_deuda_ingreso*100) %>% 
  rename("Relacion deuda/ingreso"=2,"Monto prestado"=3) %>% 
  summarise_all(list(
    n = ~sum(!is.na(.)),
    Media = ~mean(., na.rm = TRUE),
    Mediana = ~median(., na.rm = TRUE),
    sd = ~sd(., na.rm = TRUE),
    Minimo = ~min(., na.rm = TRUE),
    Maximo = ~max(., na.rm = TRUE)
  )) %>%
  pivot_longer(
    cols = everything(),
    names_to = c("variable", ".value"),
    names_pattern = "^(.*)_(n|Media|Mediana|sd|Minimo|Maximo)$"
  ) %>% 
  mutate(Media=round(Media,0),
         Mediana=round(Mediana,0),
         sd=round(sd,0),
         Maximo=round(Maximo,0))

4.1 EstadĆ­sticas descriptivas de las variables principales

La Tabla 1 resume las medidas de tendencia central y dispersión de las variables cuantitativas incluidas en el estudio.

tabla_resumen = resumen_general %>%
  select(-n) %>%  
  kable(
    format = "html",
    col.names = c("Variable", "Media", "Mediana", "Desv. EstƔndar", "Mƭnimo", "MƔximo"),
    align = c('l', 'r', 'r', 'r', 'r', 'r'),
    caption = "Tabla 1: Estadƭsticas Descriptivas de las Variables Principales del Dataset de PrƩstamos"
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width = FALSE,
    font_size = 14,
    position = "center"
  ) %>%
  row_spec(0, background = "#990000", color = "white", bold = TRUE) %>%
  column_spec(1, bold = TRUE, width = "3cm") %>%
  column_spec(2, width = "2cm") %>%
  column_spec(3, width = "2cm") %>%
  column_spec(4, width = "2.5cm") %>%
  column_spec(5, width = "2cm") %>%
  column_spec(6, width = "2cm") %>%
  footnote(
    general = "Fuente: Elaboración propia con base en datos de Lending Club",
    general_title = "Nota:",
    footnote_as_chunk = TRUE
  )

tabla_resumen
Tabla 1: Estadƭsticas Descriptivas de las Variables Principales del Dataset de PrƩstamos
Variable Media Mediana Desv. EstƔndar Mƭnimo MƔximo
Ingreso 72543 64000 39073 7000 250000
Relacion deuda/ingreso 19 19 9 0 49
Monto prestado 14850 13000 8650 1000 40000
FICO 695 687 30 642 842
Nota: Fuente: Elaboración propia con base en datos de Lending Club

En promedio, los solicitantes reportan un ingreso anual de USD 72,543, con una mediana de USD 64,000, lo que sugiere una ligera asimetría positiva en la distribución, reflejando la presencia de algunos ingresos excepcionalmente altos que elevan el promedio.

La relación deuda/ingreso presenta una media de 19%, indicando que, en promedio, los deudores destinan cerca de una quinta parte de sus ingresos al pago de obligaciones financieras. No obstante, el rango entre 0% y 50% evidencia una amplia heterogeneidad en los niveles de endeudamiento entre los solicitantes.

El monto promedio de los préstamos asciende a USD 14,850, con una desviación estÔndar de aproximadamente USD 8,645, lo que denota variabilidad significativa en los montos solicitados, posiblemente asociada a diferencias en capacidad de pago o propósito del crédito.

Por su parte, el puntaje FICO, que refleja el historial crediticio de los solicitantes, presenta una media de 697 puntos, situĆ”ndose dentro de la categorĆ­a de ā€œbuen crĆ©ditoā€. Su baja desviación estĆ”ndar (31.7) indica una distribución relativamente concentrada, lo que sugiere que la mayorĆ­a de los clientes poseen un perfil crediticio estable.

En conjunto, estas estadísticas evidencian una población de solicitantes con ingresos moderados a altos, niveles de endeudamiento diversos y un comportamiento crediticio predominantemente positivo.

4.2 Distribución individual de variables numéricas

4.2.1 Histograma del ingreso

La Figura 1 presenta la distribución de la variable ingreso anual de los solicitantes de préstamo. El histograma evidencia una asimetría positiva, donde la mayoría de los individuos reportan ingresos entre USD 40,000 y USD 80,000, mientras que un grupo reducido alcanza valores considerablemente mÔs altos, superiores a los USD 150,000.

library(gifski)
library(magick)

crear_frame <- function(percentil) {
  datos_filtrados <- Base_datos %>% 
    filter(Ingreso <= quantile(Base_datos$Ingreso, percentil, na.rm = TRUE))
  
  ggplot(datos_filtrados, aes(x = Ingreso)) +
    geom_histogram(
      binwidth = 5000,
      fill = SC["primary_red"],
      color = "white",
      alpha = 0.9
    ) +
    geom_vline(
      xintercept = median(Base_datos$Ingreso, na.rm = TRUE),
      color = SC["gold"],
      linetype = "dashed", 
      linewidth = 1.2
    ) +
    geom_vline(
      xintercept = mean(Base_datos$Ingreso, na.rm = TRUE),
      color = SC["dark_red"],
      linetype = "solid",
      linewidth = 0.8
    ) +
    scale_x_continuous(
      labels = scales::dollar_format(prefix = "$", big.mark = ","),
      limits = c(0, max(Base_datos$Ingreso, na.rm = TRUE))
    ) +
    scale_y_continuous(labels = scales::comma_format()) +
    labs(
      title = paste("Figura 1, Distribución del Ingreso -", round(percentil * 100), "% de datos"),
      x = "Ingreso Anual (USD)",
      y = "Frecuencia"
    ) +
    TS()
}


percentiles <- seq(0.1, 1, by = 0.1)
frames <- map(percentiles, crear_frame)


temp_files <- map_chr(seq_along(frames), function(i) {
  archivo <- tempfile(fileext = ".png")
  ggsave(archivo, frames[[i]], width = 10, height = 6, dpi = 100)
  archivo
})


animacion <- image_read(temp_files) %>%
  image_animate(fps = 2) %>%
  image_write("animacion_ingreso.gif")


knitr::include_graphics("animacion_ingreso.gif")

La línea roja punteada indica la mediana, ubicada alrededor de USD 65,000, lo cual coincide con la tendencia central observada en la tabla descriptiva. Esta concentración en niveles intermedios de ingreso sugiere que la base de datos estÔ compuesta principalmente por personas con capacidad de pago media, probablemente pertenecientes a segmentos laborales formales o con ingresos estables.

Los valores mÔs altos de ingreso, aunque minoritarios, representan a solicitantes con mayor capacidad financiera, lo que puede influir positivamente en su probabilidad de aprobación y cumplimiento del crédito. En conjunto, la distribución muestra una población heterogénea, pero con predominio de ingresos medios dentro del conjunto analizado.

4.2.2 Distribución de la relación deuda/ingreso

La Figura 2 ilustra la densidad de la variable relación deuda/ingreso (%), la cual mide el nivel de endeudamiento de los solicitantes respecto a su capacidad económica.

La distribución presenta una forma ligeramente asimétrica hacia la derecha, con un claro punto de concentración entre los 10% y 25%, y una mediana cercana al 18% (línea roja punteada).

mediana_dti <- median(Base_datos$Relacion_deuda_ingreso, na.rm = TRUE) * 100
media_dti <- mean(Base_datos$Relacion_deuda_ingreso, na.rm = TRUE) * 100

# Crear variable temporal para el plotting (multiplicada por 100)
Base_datos$dti_porcentaje <- Base_datos$Relacion_deuda_ingreso * 100

# Calcular densidad con los valores en porcentaje
densidad_dti <- density(Base_datos$dti_porcentaje, na.rm = TRUE)
max_densidad <- max(densidad_dti$y) * nrow(Base_datos) * 2

p_dti_interactivo <- plot_ly(Base_datos, x = ~dti_porcentaje) %>%
  add_histogram(
    name = "Frecuencia",
    nbinsx = 30,
    marker = list(
      color = SC["primary_red"],
      line = list(color = "white", width = 1)
    ),
    opacity = 0.7,
    hovertemplate = "DTI: %{x:.1f}%<br>Frecuencia: %{y}<extra></extra>"
  ) %>%
  add_lines(
    x = densidad_dti$x,
    y = densidad_dti$y * nrow(Base_datos) * 2,
    name = "Densidad",
    line = list(
      color = SC["secondary_red"],
      width = 2
    ),
    fill = 'tozeroy',
    fillcolor = paste0(SC["accent_red"], '40'),
    hovertemplate = "DTI: %{x:.1f}%<br>Densidad: %{y:.3f}<extra></extra>"
  ) %>%
  layout(
    title = list(
      text = "<b>Distribución de la Relación Deuda/Ingreso (DTI)</b>",
      font = list(
        family = "Playfair Display",
        size = 22,
        color = SC["primary_red"]
      ),
      x = 0.05
    ),
    xaxis = list(
      title = list(
        text = "Relación Deuda/Ingreso (%)",
        font = list(
          family = "Playfair Display",
          size = 14,
          color = SC["dark_red"]
        )
      ),
      tickformat = ".1f",  # Mostrar como nĆŗmeros decimales
      ticksuffix = "%",    # Agregar símbolo % después del número
      gridcolor = SC["light_red"],
      zerolinecolor = SC["light_red"],
      range = c(0, max(Base_datos$dti_porcentaje, na.rm = TRUE) * 1.05)
    ),
    yaxis = list(
      title = list(
        text = "Frecuencia / Densidad",
        font = list(
          family = "Playfair Display", 
          size = 14,
          color = SC["dark_red"]
        )
      ),
      gridcolor = SC["light_red"],
      zerolinecolor = SC["light_red"]
    ),
    shapes = list(
      # LĆ­nea de mediana
      list(
        type = "line",
        x0 = mediana_dti,
        x1 = mediana_dti,
        y0 = 0,
        y1 = max_densidad * 0.95,
        line = list(
          color = SC["gold"],
          dash = "dash",
          width = 2
        )
      ),
      # LĆ­nea de media
      list(
        type = "line",
        x0 = media_dti,
        x1 = media_dti,
        y0 = 0,
        y1 = max_densidad * 0.95,
        line = list(
          color = SC["dark_red"],
          width = 2
        )
      )
    ),
    plot_bgcolor = SC["parchment"],
    paper_bgcolor = "white",
    font = list(family = "Source Serif Pro", size = 12),
    margin = list(l = 80, r = 50, t = 80, b = 100),  # AumentƩ bottom margin para las anotaciones
    hoverlabel = list(
      font = list(family = "Source Serif Pro")
    ),
    legend = list(
      orientation = "h",
      x = 0.5,
      y = -0.2,  # AjustƩ para dar mƔs espacio
      xanchor = "center",
      font = list(family = "Source Serif Pro")
    ),
    annotations = list(
      # Anotación para mediana
      list(
        x = mediana_dti,
        y = max_densidad,
        text = paste("Mediana:", round(mediana_dti, 1), "%"),
        showarrow = FALSE,
        font = list(
          family = "Source Serif Pro",
          size = 10,
          color = SC["gold"]
        ),
        bgcolor = "white",
        bordercolor = SC["gold"],
        borderpad = 4,
        borderwidth = 1
      ),
      # Anotación para media
      list(
        x = media_dti,
        y = max_densidad * 0.85,
        text = paste("Media:", round(media_dti, 1), "%"),
        showarrow = FALSE,
        font = list(
          family = "Source Serif Pro",
          size = 10,
          color = SC["dark_red"]
        ),
        bgcolor = "white",
        bordercolor = SC["dark_red"],
        borderpad = 4,
        borderwidth = 1
      ),
      # Fuente en la parte inferior
      list(
        x = 1,
        y = -0.3,
        text = paste(
          "Fuente: Dataset Lending Club |",
          "N =", scales::comma(nrow(Base_datos)), "observaciones"
        ),
        showarrow = FALSE,
        xref = "paper",
        yref = "paper",
        xanchor = "right",
        font = list(
          family = "Source Serif Pro",
          size = 10,
          color = SC["secondary_red"]
        )
      )
    )
  ) %>%
  config(
    displayModeBar = TRUE,
    modeBarButtonsToRemove = c("pan2d", "lasso2d", "select2d"),
    displaylogo = FALSE
  )

p_dti_interactivo

Esto indica que, en promedio, los solicitantes destinan menos de una quinta parte de sus ingresos al pago de deudas, lo que refleja niveles de endeudamiento controlados en la mayorĆ­a de los casos. Sin embargo, se observan algunos valores altos por encima del 40% que corresponden a individuos con una carga financiera elevada, lo que puede representar un mayor riesgo de incumplimiento.

La forma suavizada del grÔfico evidencia una distribución continua y bien concentrada, lo que sugiere que el comportamiento de esta variable sigue una tendencia general homogénea dentro de la población crediticia.

4.2.3 Distribución del puntaje FICO

library(purrr)
mediana_fico <- median(Base_datos$FICO, na.rm = TRUE)
media_fico <- mean(Base_datos$FICO, na.rm = TRUE)

# Función para crear frames de animación
crear_frame_fico <- function(porcentaje) {
  # Filtrar datos progresivamente
  datos_anim <- Base_datos %>%
    arrange(FICO) %>%
    slice(1:round(n() * porcentaje))
  
  p <- ggplot(datos_anim, aes(x = FICO)) +
    geom_density(
      bw = 15,
      fill = SC["primary_red"],
      color = SC["dark_red"],
      alpha = 0.8,
      linewidth = 1.1
    ) +
    geom_vline(
      xintercept = mediana_fico,
      color = SC["gold"],
      linetype = "dashed", 
      linewidth = 1.2
    ) +
    geom_vline(
      xintercept = media_fico,
      color = SC["accent_red"],
      linetype = "solid",
      linewidth = 1
    ) +
    scale_x_continuous(
      breaks = seq(600, 850, 25),
      limits = c(600, 850)
    ) +
    scale_y_continuous(
      expand = expansion(mult = c(0, 0.05)),
      labels = scales::comma_format()
    ) +
    labs(
      title = paste("Distribución del Puntaje FICO -", round(porcentaje * 100), "% de datos"),
      x = "Puntaje FICO",
      y = "Densidad",
      caption = paste(
        "Mediana:", round(mediana_fico), "|",
        "Media:", round(media_fico), "|",
        "N:", scales::comma(round(nrow(Base_datos) * porcentaje)), "observaciones"
      )
    )
  
  # Aplicar el tema correctamente
  p + TS() +
    theme(
      plot.title = element_text(
        family = "Playfair Display",
        face = "bold",
        color = SC["primary_red"],
        size = 16,
        hjust = 0.5
      ),
      plot.caption = element_text(
        family = "Source Serif Pro",
        color = SC["secondary_red"],
        size = 10
      )
    )
}

# Crear secuencia de frames
porcentajes <- seq(0.1, 1, by = 0.1)
frames <- map(porcentajes, crear_frame_fico)

# Guardar frames temporales
temp_files <- map_chr(seq_along(frames), function(i) {
  archivo <- tempfile(fileext = ".png")
  ggsave(archivo, frames[[i]], width = 10, height = 6, dpi = 100, bg = "white")
  archivo
})

# Crear animación con magick
animacion_fico <- image_read(temp_files) %>%
  image_animate(fps = 2, optimize = TRUE) %>%
  image_write("animacion_fico.gif")

# Mostrar la animación
knitr::include_graphics("animacion_fico.gif")

La distribución del puntaje FICO evidencia una clara concentración de valores entre 660 y 720 puntos, con una mediana cercana a 690 (línea roja).

Esto indica que la mayorĆ­a de los solicitantes poseen un historial crediticio considerado ā€œbuenoā€, aunque no necesariamente ā€œexcelenteā€.

La distribución es ligeramente asimétrica hacia la derecha, lo cual refleja que existen prestatarios con puntajes altos (mayores a 750), pero en menor proporción.

Este comportamiento es esperable, dado que los puntajes mƔs altos suelen corresponder a individuos con un historial crediticio mƔs largo y estable.

4.2.4 Distribución del monto del préstamo

mediana_monto <- median(Base_datos$Monto_prestado, na.rm = TRUE)
media_monto <- mean(Base_datos$Monto_prestado, na.rm = TRUE)

p_monto_interactivo <- plot_ly(Base_datos, x = ~Monto_prestado) %>%
  add_histogram(
    name = "Frecuencia",
    nbinsx = 30,
    marker = list(
      color = SC["primary_red"],
      line = list(color = "white", width = 1)
    ),
    opacity = 0.9,
    hovertemplate = "Monto: %{x:$,.0f}<br>Frecuencia: %{y}<extra></extra>"
  ) %>%
  layout(
    title = list(
      text = "<b>Distribución del Monto de Préstamos</b>",
      font = list(
        family = "Playfair Display",
        size = 22,
        color = SC["primary_red"]
      ),
      x = 0.05
    ),
    xaxis = list(
      title = list(
        text = "Monto del PrƩstamo (USD)",
        font = list(
          family = "Playfair Display",
          size = 14,
          color = SC["dark_red"]
        )
      ),
      tickformat = "$,.0f",
      gridcolor = SC["light_red"],
      zerolinecolor = SC["light_red"],
      range = c(0, max(Base_datos$Monto_prestado, na.rm = TRUE) * 1.05)
    ),
    yaxis = list(
      title = list(
        text = "Frecuencia",
        font = list(
          family = "Playfair Display", 
          size = 14,
          color = SC["dark_red"]
        )
      ),
      gridcolor = SC["light_red"],
      zerolinecolor = SC["light_red"]
    ),
    shapes = list(
      # LĆ­nea de mediana
      list(
        type = "line",
        x0 = mediana_monto,
        x1 = mediana_monto,
        y0 = 0,
        y1 = 1,
        yref = "paper",
        line = list(
          color = SC["gold"],
          dash = "dash",
          width = 2
        )
      ),
      # LĆ­nea de media
      list(
        type = "line",
        x0 = media_monto,
        x1 = media_monto,
        y0 = 0,
        y1 = 1,
        yref = "paper",
        line = list(
          color = SC["dark_red"],
          width = 2
        )
      )
    ),
    plot_bgcolor = SC["parchment"],
    paper_bgcolor = "white",
    font = list(family = "Source Serif Pro", size = 12),
    margin = list(l = 80, r = 50, t = 80, b = 100),
    hoverlabel = list(
      font = list(family = "Source Serif Pro")
    ),
    annotations = list(
      # Anotación para mediana
      list(
        x = mediana_monto,
        y = 1,
        yref = "paper",
        text = paste("Mediana:", scales::dollar(mediana_monto)),
        showarrow = FALSE,
        font = list(
          family = "Source Serif Pro",
          size = 10,
          color = SC["gold"]
        ),
        bgcolor = "white",
        bordercolor = SC["gold"],
        borderpad = 4,
        borderwidth = 1
      ),
      # Anotación para media
      list(
        x = media_monto,
        y = 0.9,
        yref = "paper",
        text = paste("Media:", scales::dollar(media_monto)),
        showarrow = FALSE,
        font = list(
          family = "Source Serif Pro",
          size = 10,
          color = SC["dark_red"]
        ),
        bgcolor = "white",
        bordercolor = SC["dark_red"],
        borderpad = 4,
        borderwidth = 1
      ),
      # Fuente en la parte inferior
      list(
        x = 1,
        y = -0.15,
        text = paste(
          "Fuente: Dataset Lending Club |",
          "N =", scales::comma(nrow(Base_datos)), "observaciones"
        ),
        showarrow = FALSE,
        xref = "paper",
        yref = "paper",
        xanchor = "right",
        font = list(
          family = "Source Serif Pro",
          size = 10,
          color = SC["secondary_red"]
        )
      )
    )
  ) %>%
  config(
    displayModeBar = TRUE,
    modeBarButtonsToRemove = c("pan2d", "lasso2d", "select2d"),
    displaylogo = FALSE
  )

p_monto_interactivo

El histograma del monto solicitado en préstamo presenta una distribución dispersa, con una fuerte concentración de valores entre 5,000 y 15,000, y una mediana cercana a los 12,000.

Esto sugiere que la mayoría de los créditos aprobados corresponden a montos pequeños o medianos, probablemente asociados a consumo o consolidación de deudas.

Se observa tambiƩn la presencia de montos mƔs altos, aunque con menor frecuencia, lo cual es coherente con una polƭtica crediticia que limita el riesgo mediante montos moderados para la mayorƭa de los solicitantes.

4.3 Variable por estado de pago

4.3.1 Puntaje FICO por estado de pago

p_fico_box <- ggplot(lending_base, aes(x = estado_pago, y = puntaje_fico, fill = estado_pago)) +
geom_boxplot() +
labs(
x = "Estado de pago",
y = "Puntaje FICO"
) 
print(p_fico_box)

El boxplot evidencia una diferencia notable en el puntaje FICO segĆŗn el estado de pago. Los prestatarios que pagan tienden a tener un FICO ligeramente superior, con una mediana cercana a los 700 puntos, mientras que quienes no pagan se concentran alrededor de 680–690 puntos. Esta diferencia confirma que un mejor historial crediticio se asocia con un mayor cumplimiento en los pagos.

4.3.2 Interpretación de Resultados

Los coeficientes de correlación evidencian relaciones significativas entre las variables analizadas. Particularmente, se observa una correlación negativa pronunciada entre el peso del vehículo y su eficiencia energética (r = -0.868), consistente con hallazgos reportados en la literatura especializada.

5 Conclusiones

5.1 Hallazgos Principales

  1. Relación inversa significativa entre masa vehicular y eficiencia de combustible
  2. Patrón consistente en la distribución de características técnicas
  3. Validación empírica de los postulados teóricos establecidos

5.2 Implicaciones PrƔcticas

Los resultados obtenidos proporcionan evidencia cuantitativa para el desarrollo de polƭticas de eficiencia energƩtica y diseƱo vehicular optimizado.